package com.zys.http.processor;

import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.zys.http.constant.HttpEnum;
import com.zys.http.extension.gutter.HttpLineMarkerInfo;
import com.zys.http.tool.ThreadTool;
import com.zys.http.tool.UrlTool;
import com.zys.http.ui.tree.node.MethodNode;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zys
 * @since 2025-01-13
 */
public abstract class AbstractKotlinProcessor extends AbstractProcessor {

    // region annotation
    @Override
    @SneakyThrows
    public boolean isTargetAnnotation(PsiElement element, String qualifiedName) {
        if (qualifiedName == null || qualifiedName.isEmpty()) {
            return false;
        }

        if (!(element instanceof KtAnnotationEntry annotation)) {
            return false;
        }

        Name shortName = ReadAction.nonBlocking(annotation::getShortName).submit(ThreadTool.getExecutor()).get();
        return shortName != null && qualifiedName.endsWith(shortName.asString());
    }

    @Override
    public @NotNull List<String> annotationValues(PsiElement element, String[] attributeNames) {
        if (!(element instanceof KtAnnotationEntry annotation)) {
            return DEFAULT_ANNO_VALUE;
        }

        List<? extends ValueArgument> arguments = annotation.getValueArguments();
        if (arguments.isEmpty()) {
            return DEFAULT_ANNO_VALUE;
        }

        List<KtExpression> expressions = getKtExpressions(attributeNames, arguments);
        if (expressions.isEmpty()) {
            return DEFAULT_ANNO_VALUE;
        }

        List<KtExpression> all = new ArrayList<>();
        for (KtExpression expression : expressions) {
            if (expression instanceof KtCollectionLiteralExpression literalExpression) {
                all.addAll(literalExpression.getInnerExpressions());
            } else {
                all.add(expression);
            }
        }

        List<String> list = all.stream().map(this::expressionParse).filter(Objects::nonNull).distinct().toList();
        return list.isEmpty() ? DEFAULT_ANNO_VALUE : list;
    }

    // endregion annotation

    // region class

    @Override
    public String controllerPackageName(PsiElement element) {
        return element instanceof KtClass ktClass ? ktClass.getContainingKtFile().getPackageFqName().asString() : null;
    }

    @Override
    public Set<? extends PsiElement> controllerAnnotations(PsiElement element) {
        if (!(element instanceof KtClass ktClass)) {
            return Collections.emptySet();
        }
        KtModifierList modifierList = ktClass.getModifierList();
        return Objects.isNull(modifierList) ? Collections.emptySet() : new HashSet<PsiElement>(modifierList.getAnnotationEntries());
    }

    @Override
    public String controllerSwagger(PsiElement element) {
        return element instanceof KtClass ktClass ? getSwagger(ktClass.getModifierList(), List.of(HttpEnum.Swagger.API, HttpEnum.Swagger.TAG)) : "";
    }

    private String getSwagger(KtModifierList modifierList, List<HttpEnum.Swagger> swaggers) {
        if (Objects.isNull(modifierList)) {
            return "";
        }
        List<KtAnnotationEntry> entries = modifierList.getAnnotationEntries();
        KtAnnotationEntry entry = entries.stream().filter(o -> Objects.nonNull(o.getShortName()))
                .filter(e -> swaggers.stream().map(HttpEnum.Swagger::getClazz).anyMatch(o -> isTargetAnnotation(e, o)))
                .findFirst().orElse(null);
        if (Objects.isNull(entry)) {
            return "";
        }
        Name shortName = entry.getShortName();
        if (Objects.isNull(shortName)) {
            return "";
        }
        HttpEnum.Swagger operation = swaggers.stream().filter(o -> o.getClazz().endsWith(shortName.asString()))
                .findFirst().orElse(null);
        return Objects.isNull(operation) ? "" : annotationValue(entry, new String[]{operation.getValue()});
    }

    @Override
    public List<? extends PsiElement> controllerMethods(PsiElement element) {
        if (!(element instanceof KtClass ktClass)) {
            return Collections.emptyList();
        }
        if (ktClass.isAnnotation() || ktClass.isInterface() || ktClass.isEnum()) {
            return Collections.emptyList();
        }
        return ktClass.getDeclarations().stream()
                .filter(this::isTargetMethod)
                .collect(Collectors.toList());
    }

    @Override
    public List<MethodNode> methodNodes(PsiElement psiElement, String modulePath) {
        if (!(psiElement instanceof KtClass ktClass)) {
            return Collections.emptyList();
        }
        if (ktClass.isEnum() || ktClass.isInterface() || ktClass.isAnnotation()) {
            return Collections.emptyList();
        }

        List<String> controllerPaths = controllerPaths(ktClass);

        List<MethodNode> res = new ArrayList<>();
        List<? extends PsiElement> functions = this.controllerMethods(ktClass);
        for (String controllerPath : controllerPaths) {
            for (PsiElement f : functions) {
                List<MethodNode> methodNodes = methodNodeCreate(f, controllerPath, modulePath, false);
                if (methodNodes != null && !methodNodes.isEmpty()) {
                    res.addAll(methodNodes);
                }
            }
        }

        return res;
    }

    // endregion class

    // region method

    @Override
    public String methodSwagger(PsiElement element) {
        return element instanceof KtNamedFunction function ? getSwagger(function.getModifierList(), List.of(HttpEnum.Swagger.API_OPERATION, HttpEnum.Swagger.OPERATION)) : "";
    }

    @Override
    public List<? extends PsiElement> methodParameters(PsiElement element) {
        return element instanceof KtNamedFunction function ? function.getValueParameters() : Collections.emptyList();
    }

    // endregion method

    // region expression
    private List<KtExpression> getKtExpressions(String[] attributeNames, List<? extends ValueArgument> valueArguments) {
        List<KtExpression> expressions = new ArrayList<>();
        for (String attributeName : attributeNames) {
            for (ValueArgument argument : valueArguments) {
                ValueArgumentName argumentName = argument.getArgumentName();
                KtExpression expression = argument.getArgumentExpression();
                if (Objects.isNull(argumentName) && "value".equals(attributeName)) {
                    expressions.add(expression);
                }
                if (Objects.isNull(argumentName)) {
                    continue;
                }
                if (argumentName.getAsName().toString().equals(attributeName)) {
                    expressions.add(expression);
                }
            }
        }
        return expressions;
    }

    @Override
    public String expressionParse(PsiElement element) {
        if (element instanceof KtStringTemplateExpression templateExpression) {
            return stringTemplateExpressionParse(templateExpression);
        } else if (element instanceof KtNameReferenceExpression nameReferenceExpression) {
            return nameReferenceExpressionParse(nameReferenceExpression);
        } else if (element instanceof KtBinaryExpression binaryExpression) {
            return binaryExpressionParse(binaryExpression);
        } else if (element instanceof KtDotQualifiedExpression qualifiedExpression) {
            return dotQualifiedExpressionParse(qualifiedExpression);
        }
        return null;
    }

    private String stringTemplateExpressionParse(KtStringTemplateExpression expression) {
        KtStringTemplateEntry[] entries = expression.getEntries();
        if (entries.length == 0) {
            String text = expression.getText();
            return text.startsWith("\"") && text.endsWith("\"") ? text.substring(1, text.length() - 1) : text;
        }
        StringBuilder res = new StringBuilder();
        for (KtStringTemplateEntry entry : entries) {
            if (entry instanceof KtSimpleNameStringTemplateEntry simpleNameEntry) {
                res.append(expressionParse(simpleNameEntry.getExpression()));
            } else if (entry instanceof KtLiteralStringTemplateEntry literalEntry) {
                res.append(literalEntry.getText());
            }
        }
        return res.toString();
    }

    private String nameReferenceExpressionParse(KtNameReferenceExpression expression) {
        PsiReference reference = expression.getReference();
        if (reference == null) {
            return null;
        }
        KtProperty property = (KtProperty) reference.resolve();
        return property == null ? null : expressionParse(property.getInitializer());
    }

    private String binaryExpressionParse(KtBinaryExpression expression) {
        KtExpression left = expression.getLeft();
        StringBuilder res = new StringBuilder();
        if (left != null) {
            String s = expressionParse(left);
            if (s != null) {
                res.append(s);
            }
        }
        KtExpression right = expression.getRight();
        if (right != null) {
            String s = expressionParse(right);
            if (s != null) {
                res.append(s);
            }

        }
        return res.toString();
    }

    private String dotQualifiedExpressionParse(KtDotQualifiedExpression expression) {
        return expressionParse(expression.getSelectorExpression());
    }

    // endregion expression
    @Override
    public List<PostmanItem> postmanControllerItems(PsiElement element, String contextPath) {
        if (!isTargetController(element)) {
            return Collections.emptyList();
        }
        List<String> controllerPaths = controllerPaths(element);
        List<KtNamedFunction> functions = ((KtClass) element).getDeclarations().stream().filter(KtNamedFunction.class::isInstance).map(KtNamedFunction.class::cast).toList();
        if (functions.isEmpty()) {
            return Collections.emptyList();
        }
        List<PostmanItem> res = new ArrayList<>();
        for (String controllerPath : controllerPaths) {
            for (KtNamedFunction function : functions) {
                List<PostmanItem> postmanItems = postmanMethodItems(function, contextPath, controllerPath);
                if (Objects.nonNull(postmanItems) && !postmanItems.isEmpty()) {
                    res.addAll(postmanItems);
                }
            }
        }

        return res;
    }

    @Override
    public HttpLineMarkerInfo createLineMarkerInfo(PsiElement element) {
        if (!isTargetMethod(element)) {
            return null;
        }
        KtNamedFunction function = (KtNamedFunction) element;

        PsiElement parent = function.getParent();
        if (!(parent instanceof KtClassBody classBody)) {
            return null;
        }
        PsiElement nameIdentifier = function.getNameIdentifier();
        return isTargetController(classBody.getParent()) && nameIdentifier != null ? new HttpLineMarkerInfo(nameIdentifier) : null;
    }

    @Override
    public String apiPath(PsiElement element, Project project) {
        if (!isTargetMethod(element)) {
            return null;
        }
        KtNamedFunction function = (KtNamedFunction) element;
        PsiElement parent = function.getParent();
        if (!(parent instanceof KtClassBody)) {
            return null;
        }

        parent = parent.getParent();
        if (!(parent instanceof KtClass)) {
            return null;
        }
        Module module = ModuleUtilCore.findModuleForPsiElement(parent);
        return Objects.isNull(module) ? null : UrlTool.buildMethodUri(modulePath(module, project), controllerPaths(parent).get(0), methodPaths(function).get(0));
    }

    @Override
    public String getPresentationLocation(PsiElement element) {
        if (!isTargetMethod(element)) {
            return null;
        }

        KtNamedFunction function = (KtNamedFunction) element;
        return ApplicationManager.getApplication().runReadAction((Computable<String>) () ->
                Optional.ofNullable(function.getParent().getParent()).filter(KtClass.class::isInstance)
                        .map(KtClass.class::cast).map(NavigationItem::getName).orElse("") + "#" + function.getName()
        );
    }
}
